#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>
#include <ctime>

#define SUB_NUM 5
#define SURAND_TIME srand(time(nullptr) ^ 0x240422)
typedef void (*task_p)();

// 保存每个子进程和父进程写端文件描述符的关系
class subEp
{
public:
    subEp(int fd, pid_t id)
        : _id(id), _fd(fd)
    {
        char name[1024];
        // 进程命名：进程编号，子进程PID，读端文件描述
        snprintf(name, sizeof(name), "subProcess-%d, pid[%d], fd[%d]", _num++, _id, _fd);
        _name = name;
    }

    static int _num; // 子进程编号
    std::string _name;
    pid_t _id;
    int _fd; // 父进程对应子进程的写端描述符
};

int subEp::_num = 0;

/************************************任务表***********************/
void printTask()
{
    std::cout << getpid() << ": 打印任务\n" << std::endl;
}

void downloadTask()
{
    std::cout << getpid() << ": 下载任务\n" << std::endl;
}

void fflushTask()
{
    std::cout << getpid() << ": 刷新任务\n" << std::endl;
}

void taskTable(std::vector<task_p> &funcMap)
{
    funcMap.push_back(printTask);
    funcMap.push_back(downloadTask);
    funcMap.push_back(fflushTask);
}

/***************************************************************************/

int recvTask(int fd)
{
    int code;
    int n = read(fd, &code, sizeof(int));
    // 读取失败时，read返回-1，成功时返回读取内容的大小（字节）
    if (n == 4)
        return code;
    else if (n <= 0)
        return -1;
}

void subProcess(std::vector<subEp> &subMap, std::vector<task_p> &taskMap)
{
    std::vector<int> closeFd;

    for (int i = 0; i < SUB_NUM; i++)
    {
        // 1.创建匿名管道
        int fds[2];
        int n = pipe(fds);
        closeFd.push_back(fds[1]);

        // 2.创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            //这里子进程在拷贝父进程的文件描述符表时
            //假设此时为第3个子进程，那么3也会拷贝父进程的文件描述符表中的对1、2的匿名管道的写端。
            //即1、2的写端不只父进程有，那么只关掉附近的写端，1、2read时不会返回0，因为3的对1、2的写端还开着
            //那么1、2就不会退出，一直在阻塞等待，
            //所以要有一个数组记录父进程的写端，在子进程中将其关闭。

            for (int j = 0; j < closeFd.size(); j++)
            {
                close(closeFd[j]);
            }
            // 子进程，读
            close(fds[1]);
            while (1)
            {
                // 1.读取任务码
                int commandCode = recvTask(fds[0]);
                // 2.执行任务
                if (commandCode >= 0 && commandCode < taskMap.size())
                    taskMap[commandCode]();
                else if (commandCode == -1)
                    break;
            }

            exit(0);
        }

        // 父进程，写
        close(fds[0]);
        // 保存映射关系，维护父子的通信信道
        subMap.push_back(subEp(fds[1], id));
    }
}

void sendTask(const std::vector<task_p> &taskMap, const std::vector<subEp> &subMap, int count)
{
    int tasknum = taskMap.size(); // 防止每次循环都执行.size()
    while (1)
    {
        // 选择一个任务
        int taskIndex = rand() % tasknum;
        // 选择一个子进程执行任务
        int subIndex = rand() % SUB_NUM;
        // 发送任务码
        std::cout << "将任务码" << taskIndex << "，发送给" << subMap[subIndex]._name << std::endl;
        int n = write(subMap[subIndex]._fd, &taskIndex, sizeof(int));
        assert(n >= 0);
        sleep(1);

        // 传过来的count为0，表示一直发送任务，否则发送count次任务
        if (count)
        {
            count--;
            if (count == 0)
                break;
        }
    }

    // 发送完毕。关闭父进程写端文件描述符
    for (int i = 0; i < SUB_NUM; i++)
        //close(subMap[i]._fd);
    {
        close(subMap[i]._fd);
        //这里循环关闭时，假设上面没有closeFd，最终子进程也会全部退出
        //因为这里只有循环关闭，没有等待。  可以添加waitpid(subMap[i]._id, nullpyr, 0)观察，会咋在此阻塞。
        //最后一个子进程只有父进程有他的写端，当关闭后，最后一个子进程就退出了，其文件描述符表就销毁了，
        //那么其中存储的倒数第二个子进程的写端也没了，倒数第二个进程又关闭。实际这样倒着关闭所有写端。所有子进程能顺利退出
        //当然如果有closeFd，子进程就按顺序退出了。
    }
}

void waitSub(std::vector<subEp> &subMap)
{
    for (int i = 0; i < SUB_NUM; i++)
    {
        //这里是按顺序等待，故即使倒着退出，也是先打印子进程1的pid。
        waitpid(subMap[i]._id, nullptr, 0);
        std::cout << "等待成功：" << subMap[i]._id << std::endl;
    }
}

int main()
{
    SURAND_TIME;
    // 1.创建任务表
    std::vector<task_p> taskMap;
    taskTable(taskMap);

    // 2.创建子进程，并记录
    std::vector<subEp> subMap;
    subProcess(subMap, taskMap);

    // 3.发送某个命令给某个子进程
    int taskNum = 3;
    sendTask(taskMap, subMap, taskNum);
    
    // 4.回收子进程资源
    waitSub(subMap);

    return 0;
}


